package mini.compiler;

import mini.Program;

/** 
 * This is a convenience class that can be used for generating
 * RAL code.
 * 
 * @author Jennifer
 *
 */
public class CodeGenerator {
	
	// A meta instruction that tells the ProgramRewriter to add the size of the
	// activation record for the indicated function to the accumulator
	public static final String ADD_SIZE_OF_ACTIVATION_RECORD = "OMG";
	
	// A meta instruction that tells the ProgramRewriter that we're loading
	// something from a relative offset, but it's relative to the current
	// activation record's previous frame pointer
	public static final String LOAD_ACCUMULATOR_RELATIVE_TO_PREV_FP = "HAK";

	// Build up code here
	private StringBuilder code = new StringBuilder();
	
	// Do we need to insert a newline befre adding the next code line?
	private static boolean needNewline = false;
	
	public CodeGenerator() {
	}
	
	/**
	 * Adds an instruction (and its operand) to the code generator
	 * @param instruction An instruction (LDA, STA, CAL, etc.)
	 * @param variable An operand
	 */
	private void addInstruction(String instruction, String variable) {
		if( needNewline ) {
			appendNewline();
		}
		code.append( instruction + " " + variable );
		needNewline = true;
	}
	
	/**
	 * CAL <variable>
	 * @param variable
	 */
	public void addCallFunction( String variable ) {
		addInstruction("CAL",variable);
	}

	/**
	 * LDA <variable>
	 * @param variable
	 */
	public void addLoadAccumulator( String variable ) {
		addInstruction("LDA",variable);
	}
	
	/**
	 * LDI <variable>
	 * @param variable
	 */
	public void addLoadIndirect( String variable ) {
		addInstruction("LDI",variable);
	}
	
	/**
	 * STA <variable>
	 * @param variable
	 */
	public void addStoreAccumulator( String variable ) {
		addInstruction("STA",variable);
	}
	
	/**
	 * STI <variable>
	 * @param variable
	 */
	public void addStoreIndirect( String variable ) {
		addInstruction("STI",variable);
	}
	
	/**
	 * SUB <variable>
	 * @param variable
	 */
	public void addSubtract( String variable ) {
		addInstruction("SUB",variable);
	}
	
	/**
	 * ADD <variable>
	 * @param variable
	 */
	public void addAdd( String variable ) {
		addInstruction("ADD",variable);
	}
	
	/**
	 * MUL <variable>
	 * @param variable
	 */
	public void addMultiply( String variable ) {
		addInstruction("MUL",variable);
	}
	
	/**
	 * JMP <variable>
	 * Unconditional Jump
	 * @param variable
	 */
	public void addJMP( String variable ) {
		addInstruction("JMP",variable);
	}
	
	/**
	 * JMZ <variable>
	 * Jump on Zero
	 * @param variable
	 */
	public void addJMZ( String variable ) {
		addInstruction("JMZ",variable);
	}
	
	/**
	 * JMN <variable>
	 * Jump on Negative
	 * @param variable
	 */
	public void addJMN( String variable ) {
		addInstruction("JMN",variable);
	}
	
	/**
	 * JMI <variable>
	 * Indirect Jump
	 * @param variable
	 */
	public void addJMI( String variable ) {
		addInstruction("JMI",variable);
	}
	
	/**
	 * HLT <variable>
	 */
	public void addHalt() {
		if( needNewline ) {
			appendNewline();
		}
		code.append( "HLT" );
		needNewline = true;
	}
	
	/***
	 * Add label
	 */
	public void addLabel(String variable){
		if( needNewline ){
			appendNewline();
		}
		code.append(variable + ": ");
		needNewline=false;
	}
	
	/**
	 * Appends a chunk of code to what we're building up
	 * @param newCode
	 */
	public void appendCode( String newCode ) {
		if( needNewline ) {
			appendNewline();
		}
		code.append( newCode );
		needNewline = true;
	}
	
	/**
	 * Appends a line with a commen on it
	 * @param comment
	 */
	public void appendComment( String comment ) {
		if( needNewline ) {
			appendNewline();
		}
		code.append( "; " + comment );
		needNewline = true;
	}
	
	/**
	 * Add a newline
	 */
	public void appendNewline() {
		code.append( Program.NEWLINE );
		needNewline = false;
	}
	
	/**
	 * Return the built up code as a string
	 */
	public String getCode() {
		return code.toString();
	}

	@Override
	public String toString() {
		return getCode();
	}
	
	/**
	 * Given some instruction (LDA, JMP, etc) return true if
	 * it's a jump instruction (i.e., one that would take, in its
	 * symbolic form, some sort of location referene) and false
	 * otherwise (i.e., one that would take, in its symbolic form,
	 * a reference from the symbol table) 
	 * @return True if it's a jump instruction, false otherwise
	 */
	public static boolean isJumpInstruction( String instruction ) {
		if( instruction == null ) {
			return false;
		}
		return( instruction.equals("JMP") || 
				instruction.equals("JMZ") ||
				instruction.equals("JMN") ||
				instruction.equals("CAL"));
	}

	/**
	 * Given some instruction (LDA, JMP, etc) return true if
	 * it needs an operand
	 * @return True if it needs an operand, false otherwise
	 */
	public static boolean needsOperand( String instruction ) {
		
		// I guess null instructions don't need operands...
		if( instruction == null ) {
			return false;
		}
		
		// Everything but a halt needs an operand
		return !instruction.equals("HLT");
	}
	

	/** 
	 * HACK!!!! This is a 'meta-instruction' that was put in by a hack.
	 * We need some way to load a variable whose offset will not be
	 * relative to the current frame pointer, but to the previous
	 * frame pointer. We use this instruction code to indicate the
	 * special, hackish way of loading things.
	 * Blame Jennifer for this one, she was desperate 12 hours before
	 * it was due!
	 * @param variable
	 */
	public void addLoadAccumulatorRelativeToPrevFP(String variable) {
		addInstruction(LOAD_ACCUMULATOR_RELATIVE_TO_PREV_FP,variable);
	}

	/**
	 * Another meta-instruction. This one tells the ProgramRewriter
	 * to look up the function, figure out how big it's activation
	 * record should be, and add that size to the accumulator
	 * 
	 * @param funcid Function to plug in the values for
	 */
	public void addSizeOfActivationRecord(String funcid) {
		addInstruction( ADD_SIZE_OF_ACTIVATION_RECORD, funcid);
	}
}
